﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TechTalk.SpecFlow;
using Sai.Core.Introspection;
using Sai.Core;
using Sai.Core.Rules;
using Xunit;

namespace Sai.Specs.Steps
{
	[Binding]
	public class TypeComparisonSteps
	{
		const string oldClassKey = "oldClass";
		const string newClassKey = "newClass";

		[Given(@"a type named (.*) derived from (.*)")]
		public void GivenATypeNamedMostDerivedTypeDerivedFromBaseType(string mostDerivedType, string baseType)
		{
			ClassDeclaration oldClass = new ClassDeclaration(new TypeName(mostDerivedType));
			oldClass.DerivedFrom(new TypeName(baseType));

			ScenarioContext.Current.Set<ClassDeclaration>(oldClass, oldClassKey);
		}

		[When(@"the base type is changed to (.*)")]
		public void WhenTheBaseTypeIsChangedToBaseType(string baseType)
		{
			ClassDeclaration oldClass = ScenarioContext.Current.Get<ClassDeclaration>(oldClassKey);

			ClassDeclaration newClass = new ClassDeclaration(oldClass.TypeName);
			newClass.DerivedFrom(new TypeName(baseType));

			ScenarioContext.Current.Set<ClassDeclaration>(newClass, newClassKey);
		}

		[Then(@"the result should show that the base type has changed")]
		public void ThenTheResultShouldShowThatTheBaseTypeHasChanged()
		{
			ClassDeclaration oldClass = ScenarioContext.Current.Get<ClassDeclaration>(oldClassKey);
			ClassDeclaration newClass = ScenarioContext.Current.Get<ClassDeclaration>(newClassKey);

			ITypeDifference diff = ExamineDifferences(
				oldClass, 
				newClass, 
				new CheckClassSuperClass());

			Assert.True(diff.HasChanged);
			string xml = diff.ToXml();
		
			Assert.Contains("<InheritanceChanges>", xml);
			Assert.Contains(
				string.Format(
				"<TypeInheritanceChange From=\"{0}\" To=\"{1}\" />", 
				oldClass.BaseType.Name,
				newClass.BaseType.Name),
				xml);

			Assert.Contains("</InheritanceChanges>", xml);
		}

		[Given(@"a type named (.*) implementing (.*)")]
		public void GivenATypeNamedXImplementingY(string typeName, string interfaceName)
		{
			ClassDeclaration oldClass = null;

			if (ScenarioContext.Current.ContainsKey(oldClassKey))
			{
				oldClass = ScenarioContext.Current.Get<ClassDeclaration>(oldClassKey);
			}
			else
			{
				oldClass = new ClassDeclaration(new TypeName(typeName));
			}

			oldClass.Implementing(new TypeName(interfaceName));

			ScenarioContext.Current.Set<ClassDeclaration>(oldClass, oldClassKey);
		}

		[When(@"the interface is removed")]
		public void WhenTheInterfaceIsRemoved()
		{
			ClassDeclaration oldClass = ScenarioContext.Current.Get<ClassDeclaration>(oldClassKey);

			ClassDeclaration newClass = new ClassDeclaration(oldClass.TypeName);

			ScenarioContext.Current.Set<ClassDeclaration>(newClass, newClassKey);
		}

		[When(@"the interface is changed to (.*)")]
		public void WhenTheInterfaceIsChangedToX(string newInterfaceName)
		{
			ClassDeclaration oldClass = ScenarioContext.Current.Get<ClassDeclaration>(oldClassKey);

			ClassDeclaration newClass = new ClassDeclaration(oldClass.TypeName);
			newClass.Implementing(new TypeName(newInterfaceName));

			ScenarioContext.Current.Set<ClassDeclaration>(newClass, newClassKey);
		}

		[Then(@"the result should show that the interface has been removed")]
		public void ThenTheResultShouldShowThatTheInterfaceHasBeenRemoved()
		{
			ClassDeclaration oldClass = ScenarioContext.Current.Get<ClassDeclaration>(oldClassKey);
			ClassDeclaration newClass = ScenarioContext.Current.Get<ClassDeclaration>(newClassKey);

			ITypeDifference diff = ExamineDifferences(
				oldClass, 
				newClass, 
				new CheckClassInterfaceChanges());

			Assert.True(diff.HasChanged);
			string xml = diff.ToXml();

			Assert.Contains("<RemovedInterfaces>", xml);
			Assert.Contains(string.Format("<MemberRemovedChange Code=\"{0}\" />", oldClass.Interfaces[0].Name), xml);
			Assert.Contains("</RemovedInterfaces>", xml);
		}

		[Then(@"the result should show that the interface has been changed")]
		public void ThenTheResultShouldShowThatTheInterfaceHasBeenChanged()
		{
			ClassDeclaration oldClass = ScenarioContext.Current.Get<ClassDeclaration>(oldClassKey);
			ClassDeclaration newClass = ScenarioContext.Current.Get<ClassDeclaration>(newClassKey);

			ITypeDifference diff = ExamineDifferences(
				oldClass, 
				newClass, 
				new CheckClassInterfaceChanges());

			Assert.True(diff.HasChanged);
			string xml = diff.ToXml();

			Assert.Contains("<RemovedInterfaces>", xml);
			Assert.Contains(string.Format("<MemberRemovedChange Code=\"{0}\" />", oldClass.Interfaces[0].Name), xml);
			Assert.Contains("</RemovedInterfaces>", xml);
		}

		private ITypeDifference ExamineDifferences(ClassDeclaration first, ClassDeclaration second, IValidateClassRule rule)
		{
			ITypeDifference diff = new TypeDifference();

			rule.Validate(first, second, diff);

			return diff;
		}
	}
}
